--==========================================
-- 文件上传: 
--[[ 
    curl http://192.168.1.205/upload \
        -F 'data=@bin/bashrc/bash.sh' \
        -H 'hostname:test.exp.net' \
        -H 'filemd5:cab8acb6d32e3b01be9e19efec57fc33'
--]]
--==========================================
local upload = require("resty.upload")
-- local cjson = require("cjson")
local mstring = require("common.libString")
local fbc = require("fbsimple.fbCommon")
local resty_md5 = require("resty.md5")
local str = require("resty.string")

local request_method = ngx.var.request_method

if request_method ~= "POST" then
    ngx.log(ngx.ERR, "NOt support: ", request_method)
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

local chunk_size = ngx.var.fb_chunk_size or 4096  -- should be set to 4096 or 8192
                                                  -- for real-world settings

local form, err = upload:new(chunk_size)
if not form then
    ngx.log(ngx.WARN, "New upload task failed : ", err)
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

local form_timeout = ngx.var.fb_upload_time or 5000 -- 默认 5 sec
form:set_timeout(form_timeout)   

-- 文件保存的根路径,在nginx配置localhost配置中通过set变量定义。
local save_root_path = ngx.var.fb_root_path or "/usr/local/openresty/nginx/upload/"

-- 保存的文件对象
local file_to_save

--文件是否成功保存
local ret_save = false

-- 获取请求头,通过请求头生成文件保存路径
local save_file_md5
local save_file_root_path
local save_file_type
local headers_tab = ngx.req.get_headers()
for k, v in pairs(headers_tab) do
    if string.lower(k) == 'src-hostname' then
        ngx.log(ngx.DEBUG, "Upload source hostname : ", v)
        save_file_root_path = v
    elseif string.lower(k) == 'src-file-type' then
        ngx.log(ngx.DEBUG, "Upload file type : ", v)
        save_file_type = v
    elseif string.lower(k) == 'src-file-md5' then
        ngx.log(ngx.DEBUG, "Upload file MD5 value : ", v)
        save_file_md5 = v
    end
end

-- 生成文件存储路径
local save_file_path
if save_file_root_path then
    if save_file_type then
        save_file_path = string.gsub(save_root_path .. '/' .. save_file_root_path .. '/' .. save_file_type .. '/', "//", "/")
    else
        save_file_path = string.gsub(save_root_path .. '/' .. save_file_root_path .. '/', "//", "/")
    end
else
    save_file_path = string.gsub(save_root_path .. '/', "//", "/")
end
ngx.log(ngx.DEBUG, "Local storage path : ", save_file_path)

if not fbc.checkSaveDir(save_file_path) then
    ngx.say("Not such file or directory")
    ngx.log(ngx.ERR, "New upload task failed : create directory failed ! ")
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

local upload_file_md5 = resty_md5:new()
local upload_filename
while true do
    local typ, res, err = form:read()
    if not typ then
        ngx.say("Failed to read field 'form' : ", err)
        ngx.log(ngx.ERR, "Failed to read field 'form' : ", err)
        return
    end
    
    if typ == "header" then
        -- 开始读取 http header
        -- 解析出本次上传的文件名
        local key = res[1]
        local value = res[2]
        ngx.log(ngx.DEBUG, "HTTP HEADER's key : ", key , " ; HTTP HEADER's value: ", value)
        if key == "Content-Disposition" then
            -- 解析出本次上传的文件名
            -- form-data; name="testFileName"; filename="testfile.txt"
            local kvlist = mstring.split(value, ';')
            for _, kv in ipairs(kvlist) do
                local seg = mstring.trim(kv)
                ngx.log(ngx.DEBUG, "HTTP HEADER 'Content-Disposition' kv : ", seg )
                if seg:find("filename") then
                    local kvfile = mstring.split(seg, "=")
                    upload_filename = string.sub(kvfile[2], 2, -2)
                    if upload_filename then
                        ngx.log(ngx.DEBUG, "Upload file name : ", upload_filename )
                        local local_file_md5 = fbc.checkLocalFileMD5(save_file_path .. upload_filename)
                        if save_file_md5 == local_file_md5 then
                            ngx.say("File already exists ! ")
                            ngx.log(ngx.WARN, "File already exists : ", save_file_path .. upload_filename)
                            return
                        end
                        local tmp_save_file = save_file_path .. upload_filename .. ".tmp"
                        ngx.log(ngx.DEBUG, "Try to open the file : ", tmp_save_file)
                        file_to_save = io.open(tmp_save_file, "w+")
                        if not file_to_save then
                            ngx.say("Fail to save the file")
                            ngx.log(ngx.ERR, "Fail to open the file ", tmp_save_file)
                            return
                        end
                        ngx.log(ngx.INFO, "Start writing to file : ", tmp_save_file)
                        break
                    end
                end
            end
        end
    elseif typ == "body" then
        -- 开始读取 http body
        if file_to_save then
            ngx.log(ngx.DEBUG, tmp_save_file, "is writing.")
            file_to_save:write(res)
            upload_file_md5:update(res)
        end
    elseif typ == "part_end" then
        -- 文件写结束，关闭文件
        if file_to_save then
            ngx.log(ngx.DEBUG, "End writing to file : ", tmp_save_file)
            file_to_save:close()
            file_to_save = nil
            local digest = upload_file_md5:final()
            local upload_file_md5_sum = str.to_hex(digest)
            if upload_file_md5_sum ~= save_file_md5 then
                ngx.say("Upload file failed.")
                ngx.log(ngx.ERR, "Upload file MD check failed : \'", upload_file_md5_sum , "\' not much MD5 field \'", save_file_md5 , "\'")
                return
            end
        end
        ngx.say("Upload file saved")
        ret_save = true
    elseif typ == "eof" then
        ngx.log(ngx.DEBUG, "upload task over.")
        -- 文件读取结束
        break
    else
        ngx.log(ngx.DEBUG, "Not upload task.")
    end
end

ngx.eof() 

if ret_save then
    local local_file = save_file_path .. upload_filename
    fbc.saveDelFile(local_file)
    ngx.log(ngx.DEBUG, "Rename upload file : ", local_file , ".tmp to ", local_file)
    os.rename(local_file .. ".tmp", local_file)
    local local_md5_file = io.open(local_file .. ".md5","w")
    ngx.log(ngx.INFO, "Renew upload file md5 value : ", save_file_md5)
    local_md5_file:write("MD5:" .. save_file_md5)
    local_md5_file:flush()
    local_md5_file:close()
    ngx.log(ngx.INFO, "Upload file saved : ", local_file)
end